package client.net.io;

import client.Client;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class represents the connection the client has with the server
 *
 * @author Vincent Tsuei
 * @version 2012-11-14
 */
public class ServerConnection implements Runnable {

    // Socket variables
    private final SocketAddress ServerAddress;
    private Socket Socket;
    
    // reference to the client
    private final Client client;    
    
    // IO Handling classes
    private ClientInputHandler inputHandler;
    private ClientOutputHandler outputHandler;
    private Thread inputThread, outputThread;
    
    // Shutdown flag
    private boolean shutdown = false;
    

    /**
     * Initialize a server connection, but don't run it yet.
     */
    public ServerConnection(SocketAddress addr, Client c) {
        if (addr == null) {
            throw new NullPointerException("Remote server address is null.");
        }
        if (c == null) {
            throw new NullPointerException("Client is null.");
        }
        if (c.getServerConnection() != null) {
            throw new IllegalStateException("Trying to overwrite a client server connection.");
        }
        ServerAddress = addr;
        client = c;
        Socket = new Socket();
        
    }

    @Override
    public void run() {
        if (isShutdown()) {
            throw new IllegalStateException("Trying to run a shutdown ServerConnection.");
        }
        
        if (isConnected()) {
            throw new RuntimeException("Trying to run an already connected ServerConnection.");
        }
        
        // Try and connect. If we can't connect, throw a RuntimeException.
        // No matter what, notify the NimClient afterwards.
        try {
            try {
                Socket.setReuseAddress(true);
                Socket.connect(ServerAddress);
            } catch (IOException ex) {
                client.getUserIO().write("Connection to server failed ...", true);
                Logger.getLogger(ServerConnection.class.getName()).log(Level.FINE, "Connection to server failed. Closing the socket...", ex);
                try {Socket.close();} catch (IOException ex1) {}
                return;
            }
            
            //Start handler threads.
            try {
                inputHandler = new ClientInputHandler(this.getClient(), Socket.getInputStream());
                inputThread = new Thread(inputHandler, "InputHandlerThread");
                outputHandler = new ClientOutputHandler(this.getClient(), Socket.getOutputStream());
                outputThread = new Thread(outputHandler, "OutputHandlerThread");
                inputThread.start();
                outputThread.start();
            } catch (IOException ex) {
                Logger.getLogger(ServerConnection.class.getName()).log(Level.SEVERE, "Failed to create Input/Output handler threads.", ex);
            }            
        } finally {
            synchronized (client) {
                client.notifyAll();
            }
        }
    }
    
    /**
     * Returns whether or not the socket is connected and NOT closed.
     */
    public boolean isConnected() {
        return Socket.isConnected() && !Socket.isClosed();
    }
    
    /**
     * Shutdown the connection to the server.
     */
    public void shutdown(boolean shutdownInput) {
        shutdown = true;
        
        //First set the shutdown flags. Then interrupt the thread.
        if (shutdownInput) {
            if (inputHandler != null) {
                inputHandler.shutdown();
            }
            if (inputThread != null) {
                inputThread.interrupt();
            }
        }
        if (outputHandler != null) {
            outputHandler.shutdown();
        }
        if (outputThread != null) {
            outputThread.interrupt();
        }
               
        try {
            Socket.close();
        } catch (IOException ex) {
        }
        client.notifyConnectionShutdown();
    }
    
    /**
     * Reset the connection.
     */
    public void reset() {
        if (!Socket.isClosed()) {
            throw new RuntimeException("Trying to reset a non-closed ServerConnection");
        }
        try {Socket.close();} catch (IOException ex) {}
        Socket = new Socket();
    }
    
    /**
     * Return the client associated with this server connection. 
     */
    public Client getClient() {
        return client;
    }
    
    /**
     * Return the address and port of the server.
     */
    public String getAddressAndPortString() {
        return Socket.getInetAddress() + ":" + Socket.getPort();
    }
    
    /**
     * Return the input handler for this connection.
     */
    public ClientInputHandler getInputHandler() {
        return inputHandler;
    }
            
    /**
     * Return the output handler for this connection.
     */
    public ClientOutputHandler getOutputHandler() {
        return outputHandler;
    }

    /**
     * Return whether this ServerConnection has been shutdown.
     */
    public boolean isShutdown() {
        return shutdown;
    }

    /**
     * Tell this connection that the input stream has shutdown.
     * 
     * The proper response is just to shut this entire connection down.
     */
    public void notifyInputShutdown() {
        shutdown(false);
    }
}
